using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class lwAstarGraph : MonoBehaviour
{
	public delegate lwAstarNode StartNodeDelegate(List<lwAstarNode> nodelist);
	public StartNodeDelegate GetStartNodeDelegate = null;

	public delegate float TimeDelegate();
	public TimeDelegate GetTimeDelegate = null;
	
	[SerializeField]
	private List<lwAstarNode> m_AllNodes = null;

	public lwAstarPathFinder m_PathFinder = null;
	
	public lwAstarNode m_DefaultStartNode = null;


	private static lwAstarGraph s_Instance = null;
	public static lwAstarGraph Instance
	{
		get
		{
			if(s_Instance == null)
			{
				return SelectGraph( 0 );
			}
			return s_Instance;
		}
	}
	
	public static lwAstarGraph SelectGraph( int nIndex )
	{
		lwAstarGraph[] graphArray = FindSceneObjectsOfType(typeof(lwAstarGraph)) as lwAstarGraph[];
		if(graphArray.Length==0)
		{
			Debug.LogError("no Astar_Graph found ... (THIS IS VERY BAD)");
			return null;
		}
		s_Instance = graphArray[nIndex];
		return s_Instance;
	}
	
	public static lwAstarGraph SelectGraph( string sGraphName )
	{
		lwAstarGraph[] graphArray = FindSceneObjectsOfType(typeof(lwAstarGraph)) as lwAstarGraph[];
		for( int i=0; i<graphArray.Length; i++ )
		{
			if ( graphArray[i].name	== sGraphName )
				return SelectGraph( i );
		}
		Debug.LogError("Astar_Graph " + sGraphName + " not found!");
		return null;
	}

	public int GetNodeCount()
	{
		return m_AllNodes.Count;
	}
	
	public lwAstarNode GetNode(int index)
	{
		if( m_AllNodes.Count == 0 )
		{
			RebuildNodeList();
		}
		return m_AllNodes[index];
	}
	
	public void AddNode(lwAstarNode node)
	{
		m_AllNodes.Add(node);
	}
	
	
	void Start()
	{
		RebuildNodeList();

		if (m_PathFinder==null)
		{
			m_PathFinder = new lwAstarPathFinder();
			m_PathFinder.m_AllNodes = m_AllNodes;
		}

	}

	public float GetTime()
	{
		if(GetTimeDelegate!=null)
			return GetTimeDelegate();
		else
			return Time.realtimeSinceStartup;
	}
	
	public lwAstarNode GetStartNode()
	{
		if( GetStartNodeDelegate != null )
		{
			return GetStartNodeDelegate(m_AllNodes);
		}
		else if( m_DefaultStartNode != null)
		{
			return m_DefaultStartNode;
		}
		else
		{
			return m_AllNodes[0];
		}
	}

	//System.Predicate<lwAstarNode>
	public static bool GotIndex(lwAstarNode node)
	{
		return node.index >= 0 && node.index<lwAstarGraph.Instance.GetNodeCount();
	}
	
	public void RebuildNodeList()
	{
		int previousCount = -1;
		if (m_AllNodes == null)
			m_AllNodes = new List<lwAstarNode>();
		else
			previousCount = m_AllNodes.Count;
		m_AllNodes.Clear();


//		lwAstarNode[] allnodes = GameObject.FindObjectsOfType(typeof(lwAstarNode)) as lwAstarNode[];
		lwAstarNode[] allnodes = transform.GetComponentsInChildren<lwAstarNode>();
		m_AllNodes.Capacity = allnodes.Length;
		if ((new List<lwAstarNode>(allnodes)).TrueForAll(GotIndex) && ((previousCount == allnodes.Length) || (previousCount==-1)))
		{
			foreach (lwAstarNode node in allnodes)
			{
				if (node.transform.parent != transform)
				{
					Debug.LogWarning("Astar_Node must be direct children of Astar_Graph re-parenting node");
					node.transform.parent = transform;
				}
				m_AllNodes.Insert(node.index, node);
#if UNITY_EDITOR
				node.destroyDelegate = UnlinkNode;
#endif
			}
		}
		else
		{
			int i = 0;
			foreach (lwAstarNode node in allnodes)
			{
				if (node.transform.parent != transform)
				{
					Debug.LogWarning("Astar_Node must be direct children of Astar_Graph re-parenting node");
					node.transform.parent = transform;
				}
				m_AllNodes.Add(node);
#if UNITY_EDITOR
				node.destroyDelegate = UnlinkNode;
#endif
				node.index = i;
				node.gameObject.name = "N" + i++;
			}
		}		
	}

	public lwAstarNode FindNearestNode2D( Vector2 vPos )
	{
		lwAstarNode node = null;
		float fMinDist = float.MaxValue;
		foreach( lwAstarNode n in m_AllNodes )
		{
			if ( !n.blocked )
			{
				Vector2 v = (Vector2)n.transform.localPosition - vPos;
				float fDist = v.SqrMagnitude();
				if ( fDist < fMinDist )
				{
					node = n;
					fMinDist = fDist;
				}
			}
		}
		return node;
	}
	
	public List<lwAstarNode> FindNodesInRange2D( Vector2 vPos, float fRange )
	{
		List<lwAstarNode> nodes = new List<lwAstarNode>();
		float fSquareRange = fRange*fRange;
		foreach( lwAstarNode n in m_AllNodes )
		{
			if ( !n.blocked )
			{
				Vector2 v = (Vector2)n.transform.localPosition - vPos;
				float fDist = v.SqrMagnitude();
				if ( fDist <= fSquareRange )
				{
					nodes.Add( n );
				}
			}
		}
		return nodes;
	}

#if UNITY_EDITOR
	public void UnlinkNode(lwAstarNode node)
	{
		if(!Application.isEditor)
		{
			Debug.LogError("UnlinkNode called in non editor application");
		}

		List<lwAstarNode.lwAstarLink> tmp = new List<lwAstarNode.lwAstarLink>();
		if( m_AllNodes.Contains( node ) )
		{
			m_AllNodes.Remove( node );
			foreach( lwAstarNode child in m_AllNodes )
			{
				if( child.index>node.index )
				{
					child.index--;
					child.gameObject.name = "N" + child.index;
				}
			}
			foreach( lwAstarNode child in m_AllNodes )
			{
				foreach( lwAstarNode.lwAstarLink link in child.m_Links )
				{
					if( link.dest!=node )
					{
						tmp.Add( link );
					}
				}
				if( tmp.Count<child.m_Links.Length )
				{
					child.m_Links = tmp.ToArray();
				}
				tmp.Clear();
				foreach( lwAstarNode.lwAstarLink link in child.m_Links )
				{
					if( link.m_destIndex>=node.index )
					{
						link.m_destIndex--;
						link.dest.index = link.m_destIndex;
					}
				}
			}
		}
	}

	//void Awake()
	//{
	//    EditorApplication.update = UpdateNodesAndLinks;
	//}

	void Update()
	{
		if (!Application.isPlaying)
		{
//			Debug.Log("UpdateGraph");
			UpdateNodesAndLinks();
		}
	}

	void UpdateNodesAndLinks()
	{
		RebuildNodeList();
		UpdateLinks();
	}
	
#endif
	public void UpdateLinks()
	{
		if (m_AllNodes != null)
		{
			foreach (lwAstarNode child in m_AllNodes)
			{
				foreach (lwAstarNode.lwAstarLink link in child.m_Links)
				{
					link.UpdateLink(child);
				}
			}
		}
	}
	
	public void GenerateGrid(int nX, int nY, Vector3 vX, Vector3 vY)
	{
		lwAstarNode[][] nodeArray = new lwAstarNode[nX][];
		
		//create all nodes
		for(int i = 0; i<nX; ++i)
		{
			nodeArray[i] = new lwAstarNode[nY];
			for (int j = 0; j < nY; ++j)
			{
				GameObject go = new GameObject("node_" + i.ToString() + "_" + j.ToString());
				nodeArray[i][j] = go.AddComponent<lwAstarNode>();
				go.transform.parent = transform;
				go.transform.localPosition = new Vector3(i * vX.x + j * vY.x, i * vX.y + j * vY.y, i * vX.z + j * vY.z);
			}
		}

		//link them in 8 directions
		for (int i = 0; i < nX; ++i)
		{
			for (int j = 0; j < nY; ++j)
			{
				lwAstarNode current = nodeArray[i][j];
			
				List<lwAstarNode.lwAstarLink> links = new List<lwAstarNode.lwAstarLink>();
				if (i > 0)
				{
					if (j > 0)
					{
						lwAstarNode.lwAstarLink BottomLeft = new lwAstarNode.lwAstarLink(nodeArray[i - 1][j - 1]);
						//BottomLeft.dest = nodeArray[i - 1][j - 1];
						links.Add(BottomLeft);
					}
					if (j < nY - 1)
					{
						lwAstarNode.lwAstarLink TopLeft = new lwAstarNode.lwAstarLink(nodeArray[i - 1][j + 1]);
						//TopLeft.dest = nodeArray[i - 1][j + 1];
						links.Add(TopLeft);
					}
					lwAstarNode.lwAstarLink Left = new lwAstarNode.lwAstarLink(nodeArray[i - 1][j]);
					//Left.dest = nodeArray[i - 1][j];
					links.Add(Left);
				}
				if (i < nX - 1)
				{
					if (j > 0)
					{
						lwAstarNode.lwAstarLink BottomRight = new lwAstarNode.lwAstarLink(nodeArray[i + 1][j - 1]);
						//BottomRight.dest = nodeArray[i + 1][j - 1];
						links.Add(BottomRight);
					}
					if (j < nY - 1)
					{
						lwAstarNode.lwAstarLink TopRight = new lwAstarNode.lwAstarLink(nodeArray[i + 1][j + 1]);
						//TopRight.dest = nodeArray[i + 1][j + 1];
						links.Add(TopRight);
					}
					lwAstarNode.lwAstarLink Right = new lwAstarNode.lwAstarLink(nodeArray[i + 1][j]);
					//Right.dest = nodeArray[i + 1][j];
					links.Add(Right);
				}

				if (j > 0)
				{
					lwAstarNode.lwAstarLink Bottom = new lwAstarNode.lwAstarLink(nodeArray[i][j - 1]);
					//Bottom.dest = nodeArray[i][j - 1];
					links.Add(Bottom);
				}
				if (j < nY - 1)
				{
					lwAstarNode.lwAstarLink Top = new lwAstarNode.lwAstarLink(nodeArray[i][j + 1]);
					//Top.dest = nodeArray[i][j + 1];
					links.Add(Top);
				}
				current.m_Links = links.ToArray();
			}
		}
		RebuildNodeList();
		UpdateLinks();
	}
}

